﻿namespace IronLua.Parsing

type TokenType = 
    | Any = 0
    | EOS = 1
    | Comment = 2
    | Identifier = 3
    | Number = 5
    | String = 6
    | Nil = 7 
    | True = 8
    | False = 9
    | Keyword = 10
    | Operator = 11
    | Separator = 12
    | VarArgs = 13

type TokenPosition =
    struct
        static member Empty =   TokenPosition(0, 1, 1, 0)
        static member Virtual = TokenPosition(0, 0, 0, 0, true)
        static member Merge (t1 : TokenPosition) (t2 : TokenPosition) = 
            if t1.IsVirtual && t2.IsVirtual then
                failwith "Unable to merge positions of virtual tokens."
            if t1.Index = t2.Index then 
                t1
            else
                if t2.Index < t1.Index then 
                    TokenPosition(t2.Index, t2.Line, t2.Column, t1.Index - t2.Index)
                else
                    TokenPosition(t1.Index, t1.Line, t1.Column, t2.Index - t1.Index)

        val Length : int
        val Index : int
        val Line : int
        val Column : int
        val IsVirtual : bool
        new(index : int, line : int, col : int, len : int) = { Length = len; Index = index; Line = line; Column = col; IsVirtual = false }
        new(index : int, line : int, col : int, len : int, virt : bool) = { Length = len; Index = index; Line = line; Column = col; IsVirtual = virt }

        override this.ToString() : string = (sprintf "[line %i, col %i, len %i]" this.Line this.Column this.Length)
    end

type Token =
    struct

        static member Empty = Token(TokenType.Any, TokenPosition.Empty)  
        static member MakeEos (t : Token) : Token = 
            let oldpos = t.Position
            let pos = TokenPosition(oldpos.Index + 1, oldpos.Line, oldpos.Column, 0) 
            Token(TokenType.EOS, pos)
              
        val Type : TokenType      
        val Position : TokenPosition               
        val LuaValue : obj option
        val Terminal : string

        new(type' : TokenType, pos : TokenPosition, value : obj) = { 
            Type = type'; 
            Position = pos; 
            LuaValue = if value <> null then Some(value) else None;
            Terminal = match type' with
                       | TokenType.Any
                       | TokenType.Comment 
                       | TokenType.Number
                       | TokenType.String
                       | TokenType.EOS ->          sprintf "(%s)" (type'.ToString().ToLower()) // special name
                       | TokenType.Nil ->          "nil"
                       | TokenType.Keyword
                       | TokenType.True
                       | TokenType.False
                       | TokenType.Separator
                       | TokenType.Operator
                       | TokenType.VarArgs
                       | TokenType.Identifier ->   string value                                // value
                       | _ ->                       failwith (sprintf "Unknown token \"%s\" at line %i, column %i." 
                                                                      (string value) 
                                                                      pos.Line 
                                                                      pos.Column)
        }
        new(type' : TokenType,  pos : TokenPosition) = new Token(type', pos, null)

        override this.ToString() = 
            match this.LuaValue with 
            | Some(obj) ->  (sprintf "%A %A [%A]" this.Type this.Position obj) 
            | None ->       (sprintf "%A %A" this.Type this.Position)
    end

type TokenMatch = 
    | Success of Token * TokenPosition
    | Consume of TokenPosition
    
type TokenRule =
    abstract Match : string -> TokenPosition -> TokenMatch option
